﻿using System;
using System.Collections.Generic; 
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;
using Microsoft.Win32;

namespace VsTortoiseSVN
{
    public static class Win32
    {
        public const int WM_KEYDOWN = 0x100;
        public const int WM_KEYUP = 0x101;

        /// <summary>
        /// This is the maximum number of characters, usually including a null terminator, 
        /// of a standard full path filename for most Windows API functions that operate on filenames.
        /// </summary>
        public const int MAX_PATH = 260;

        [StructLayout(LayoutKind.Sequential)]
        public struct SHFILEINFO
        {
            public IntPtr hIcon;
            public IntPtr iIcon;
            public uint dwAttributes;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string szDisplayName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
            public string szTypeName;
        };

        public const uint SHGFI_ICON = 0x100;
        public const uint SHGFI_LARGEICON = 0x0; // 'Large icon
        public const uint SHGFI_SMALLICON = 0x1; // 'Small icon

        [Flags]
        public enum InternetConnectionState : int
        {
            INTERNET_CONNECTION_MODEM = 0x1,
            INTERNET_CONNECTION_LAN = 0x2,
            INTERNET_CONNECTION_PROXY = 0x4,
            INTERNET_RAS_INSTALLED = 0x10,
            INTERNET_CONNECTION_OFFLINE = 0x20,
            INTERNET_CONNECTION_CONFIGURED = 0x40
        }

        [DllImport("wininet.dll", CharSet = CharSet.Auto)]
        public static extern bool InternetGetConnectedState(out InternetConnectionState lpdwFlags, int dwReserved);

        [DllImport("shell32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);

        [DllImport("user32.dll")]
        public static extern int DestroyIcon(IntPtr hIcon);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern int SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, IntPtr windowTitle);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetWindowRect(IntPtr window, out Rect bounds); 

        public static Rectangle GetWindowRectangle(IntPtr window)
        {
            Rect rect;
            if (!GetWindowRect(window, out rect))
                return Rectangle.Empty;
            else
                return Rectangle.FromLTRB(rect.left, rect.top, rect.right, rect.bottom);
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct Rect
        {
            public int left, top, right, bottom;
        }

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.U4)]
        public static extern int GetLongPathName([MarshalAs(UnmanagedType.LPTStr)] string lpszShortPath, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszLongPath, [MarshalAs(UnmanagedType.U4)] int cchBuffer);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern uint GetShortPathName([MarshalAs(UnmanagedType.LPTStr)] string lpszLongPath, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszShortPath, uint cchBuffer);

        public static System.Drawing.Icon GetSmallIcon(string filePath)
        {
            return GetIcon(filePath, SHGFI_SMALLICON);
        }

        public static System.Drawing.Icon GetLargeIcon(string filePath)
        {
            return GetIcon(filePath, SHGFI_LARGEICON);
        }

        private static System.Drawing.Icon GetIcon(string filePath, uint flags)
        {
            SHFILEINFO shinfo = new SHFILEINFO();
            IntPtr hImgSmall = SHGetFileInfo(filePath, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | flags);

            if (hImgSmall != IntPtr.Zero)
                DestroyIcon(hImgSmall);

            if(shinfo.hIcon != IntPtr.Zero)
            {
                System.Drawing.Icon icon = (System.Drawing.Icon)System.Drawing.Icon.FromHandle(shinfo.hIcon).Clone();
                DestroyIcon(shinfo.hIcon);
                return icon;
            }

            return System.Drawing.Icon.ExtractAssociatedIcon(filePath);
        }

        [DllImport("uxtheme.dll", CharSet = CharSet.Unicode)]
        private static extern int SetWindowTheme(IntPtr hWnd, String pszSubAppName, String pszSubIdList);

        /// <summary>
        /// Gives a control the appearance of a Windows Explorer list
        /// </summary>
        public static void SetExplorerTheme(System.Windows.Forms.Control control)
        {
            SetDoubleBuffered(control, true);

            try
            {
                SetWindowTheme(control.Handle, "Explorer", null);
            }
            catch
            {
                // catch any exception that would occur when the uxtheme.dll is missing
            }
        }

        /// <summary>
        /// Enables or disables double buffering for the specified control.
        /// </summary>
        public static void SetDoubleBuffered(System.Windows.Forms.Control control, bool enable)
        {
            System.Reflection.PropertyInfo propertyInfo = typeof(System.Windows.Forms.Control).GetProperty(
                        "DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

            if(propertyInfo != null)
                propertyInfo.SetValue(control, enable, null);
        }

        public static void CreateProcess(string filename, string arguments)
        {
            if (arguments.Length > 32767)
            {
                // http://blogs.msdn.com/b/oldnewthing/archive/2003/12/10/56028.aspx
                // The maximum command line length for the CreateProcess function is 32767 characters.
                // This limitation comes from the UNICODE_STRING structure. 
                Output.Instance.WriteLine(string.Format("The following argument-string is {0} characters long. The maximum command line length for the CreateProcess function is 32767 characters.", arguments.Length));
            }

            Output.Instance.WriteLine(string.Format("{0} {1}", filename, arguments));

            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.EnableRaisingEvents = false;
            proc.StartInfo.FileName = filename;
            proc.StartInfo.Arguments = arguments;
            proc.StartInfo.UseShellExecute = false;
            proc.Start();
        }

        /// <summary>
        /// Opens a file with its associated application.
        /// </summary>
        public static void ShellExecute(string filename, string arguments)
        {
            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.EnableRaisingEvents = false;
            proc.StartInfo.FileName = filename;
            proc.StartInfo.Arguments = arguments;
            proc.StartInfo.UseShellExecute = true;
            proc.Start();
        }

        /// <summary>
        /// Opens the Windows Explorer and selects the file specified by fileName.
        /// </summary>
        public static void ExploreTo(string fileName)
        {
            string args = "/select," + fileName;
            ShellExecute("explorer.exe", args);
        }

        #region Registry

        [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
        public static extern int RegOpenKeyEx(UIntPtr hKey, string subKey, int ulOptions, int samDesired, out UIntPtr hkResult);

        // This signature will not get an entire REG_BINARY value. It will stop at the first null byte.
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "RegQueryValueExW", SetLastError = true)]
        public static extern int RegQueryValueEx(UIntPtr hKey, string lpValueName, int lpReserved, out uint lpType, System.Text.StringBuilder lpData, ref uint lpcbData);

        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern int RegCloseKey(UIntPtr hKey);

        public static readonly UIntPtr HKEY_LOCAL_MACHINE = new UIntPtr(0x80000002u);
        public static readonly UIntPtr HKEY_CURRENT_USER = new UIntPtr(0x80000001u);

        public const int KEY_READ = 0x00020019;
        public const int KEY_WOW64_32KEY = 0x0200;
        public const int KEY_WOW64_64KEY = 0x0100;

        public static bool RegReadString(out string dest, UIntPtr rootKey, int samDesired, string keyPath, string valueName)
        {
            RegistryValueKind regType;
            if (RegReadKey(out dest, out regType, rootKey, samDesired, keyPath, valueName))
            {
                bool isString = regType == RegistryValueKind.String;
                return isString;
            }

            return false;
        }

        public static bool RegReadKey(out string dest, out RegistryValueKind regType, UIntPtr rootKey, int samDesired, string keyPath, string valueName)
        {
            UIntPtr hKey;
            bool success = false;

            dest = null;
            regType = RegistryValueKind.Unknown;

            if (Win32.RegOpenKeyEx(rootKey, keyPath, 0, samDesired, out hKey) == 0)
            {
                try
                {
                    uint type = 0;
                    uint size = 0;
                    if (Win32.RegQueryValueEx(hKey, valueName, 0, out type, null, ref size) == 0)
                    {
                        StringBuilder keyBuffer = new StringBuilder(1 + (int)size);
                        if (Win32.RegQueryValueEx(hKey, valueName, 0, out type, keyBuffer, ref size) == 0)
                        {
                            dest = keyBuffer.ToString();
                            regType = (RegistryValueKind)type;
                            success = true;
                        }
                    }
                }
                finally
                {
                    Win32.RegCloseKey(hKey);
                }
            }

            return success;
        }
        #endregion

        #region IsWow64

        /// <summary>
        /// The function determines whether the current operating system is a 
        /// 64-bit operating system.
        /// </summary>
        /// <returns>
        /// The function returns true if the operating system is 64-bit; 
        /// otherwise, it returns false.
        /// </returns>
        public static bool IsWow64()
        {
            if (IntPtr.Size == 8)  // 64-bit programs run only on Win64
            {
                return true;
            }
            else  // 32-bit programs run on both 32-bit and 64-bit Windows
            {
                // Detect whether the current process is a 32-bit process 
                // running on a 64-bit system.
                bool flag;
                return ((DoesWin32MethodExist("kernel32.dll", "IsWow64Process") &&
                    IsWow64Process(GetCurrentProcess(), out flag)) && flag);
            }
        }

        /// <summary>
        /// The function determins whether a method exists in the export 
        /// table of a certain module.
        /// </summary>
        /// <param name="moduleName">The name of the module</param>
        /// <param name="methodName">The name of the method</param>
        /// <returns>
        /// The function returns true if the method specified by methodName 
        /// exists in the export table of the module specified by moduleName.
        /// </returns>
        static bool DoesWin32MethodExist(string moduleName, string methodName)
        {
            IntPtr moduleHandle = GetModuleHandle(moduleName);
            if (moduleHandle == IntPtr.Zero)
            {
                return false;
            }
            return (GetProcAddress(moduleHandle, methodName) != IntPtr.Zero);
        }

        [DllImport("kernel32.dll")]
        static extern IntPtr GetCurrentProcess();

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        static extern IntPtr GetModuleHandle(string moduleName);

        [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr GetProcAddress(IntPtr hModule,
            [MarshalAs(UnmanagedType.LPStr)]string procName);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool IsWow64Process(IntPtr hProcess, out bool wow64Process);

        #endregion
    }
}
